﻿/*  This file is part of PostreSQL Utilities.

    This is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with PostreSQL Utilities.  If not, see <http://www.gnu.org/licenses/>.
  
    Author : Riaan van der Westhuizen (riaan@definitive-software.com)
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.IO;

namespace PostgreSQL
{
    /// <summary>
    /// Abstract class for the PostgreSQL utilities
    /// http://www.postgresql.org/docs/9.3/static/app-pgdump.html
    /// http://www.postgresql.org/docs/9.3/static/app-pgrestore.html
    /// http://www.postgresql.org/docs/9.3/static/app-createdb.html
    /// http://www.postgresql.org/docs/9.3/static/app-dropdb.html
    /// </summary>
    public abstract class Utilty
    {
        #region Members
        private string host = "localhost";
        private int port = 5432;
        private string database = "";
        private string username = "";
        private string password = "";
        private string utilityPath = String.Empty;
        private string utilityFile = String.Empty;
        private bool clean = false;
        private bool create = false;
        private DataMode dumpMode = DataMode.All;
        #endregion

        #region Constructors
        /// <summary>
        /// Default Constructors
        /// </summary>
        public Utilty()
        { }

        /// <summary>
        /// Constructors with Connection Info.
        /// </summary>
        /// <param name="host">Specifies the host name of the machine on which the server is running.</param>
        /// <param name="port">Specifies the TCP port or local Unix domain socket file extension on which the server is listening for connections.</param>
        /// <param name="database">Specifies the name of the database to connect to.</param>
        /// <param name="username">User name to connect as.</param>
        /// <param name="password">User password.</param>
        public Utilty(string host, int port, string database, string username, string password)
        {
            this.host = host;
            this.port = port;
            this.database = database;
            this.username = username;
            this.password = password;
        }
        #endregion

        #region Connection Parameters
        /// <summary>
        /// Specifies the host name of the machine on which the server is running.
        /// If the value begins with a slash, it is used as the directory for the Unix domain socket.
        /// -h host
        /// --host=host
        /// </summary>
        public string Host
        {
            get { return host; }
            set { host = value; }
        }

        /// <summary>
        /// Specifies the TCP port or local Unix domain socket file extension on which the server is listening for connections. Defaults to 5432
        /// -p port
        /// --port=port
        /// </summary>
        public int Port
        {
            get { return port; }
            set { port = value; }
        }

        /// <summary>
        /// Specifies the name of the database to connect to. This is equivalent to specifying dbname as the first non-option argument on the command line.
        /// If this parameter contains an = sign or starts with a valid URI prefix (postgresql:// or postgres://), it is treated as a conninfo string.
        /// -d dbname
        /// --dbname=dbname
        /// </summary>
        public string Database
        {
            get { return database; }
            set { database = value; }
        }

        /// <summary>
        /// User name to connect as.
        /// -U username
        /// --username=username
        /// </summary>
        public string UserName
        {
            get { return username; }
            set { username = value; }
        }

        /// <summary>
        /// User password.
        /// </summary>
        public string Password
        {
            get { return password; }
            set { password = value; }
        }
        #endregion

        #region Default Options
        /// <summary>
        /// Clean (drop) database objects before recreating them. (This might generate some harmless error messages, if any objects were not present in the destination database.)
        /// -c
        /// --clean
        /// </summary>
        public bool Clean
        {
            get { return clean; }
            set { clean = value; }
        }

        /// <summary>
        /// Create the database before restoring into it. If --clean is also specified, drop and recreate the target database before connecting to it.
        /// When this option is used, the database named with -d is used only to issue the initial DROP DATABASE and CREATE DATABASE commands. 
        /// All data is restored into the database name that appears in the archive.
        /// -C
        /// --create
        /// </summary>
        public bool Create
        {
            get { return create; }
            set { create = value; }
        }

        /// <summary>
        /// Data dump mode
        /// </summary>
        public DataMode DumpMode
        {
            get { return dumpMode; }
            set { dumpMode = value; }
        }
        #endregion

        #region Utility
        /// <summary>
        /// Path where to find pg_dump and pg_restore
        /// </summary>
        public string UtilityPath
        {
            get { return utilityPath; }
            set { utilityPath = value; }
        }

        /// <summary>
        /// Excuteable File name for  pg_dump and pg_restore
        /// </summary>
        public string UtilityFile
        {
            get { return utilityFile; }
            set { utilityFile = value; }
        }
        #endregion

        #region Arguments
        public abstract string Arguments();
        #endregion

        #region Execute
        public void Execute()
        {
            ProcessStartInfo procInfo = new ProcessStartInfo();
            procInfo.WorkingDirectory = utilityPath;
            procInfo.FileName = Path.Combine(utilityPath, utilityFile);
            procInfo.CreateNoWindow = true;
            procInfo.UseShellExecute = false;
            procInfo.RedirectStandardOutput = true;
            procInfo.RedirectStandardError = true;
            procInfo.Arguments = BuildArguments();
            procInfo.EnvironmentVariables.Add("PGPASSWORD", password);

            Process proc = new Process();
            proc.StartInfo = procInfo;
            proc.Start();

            string procErr = proc.StandardError.ReadToEnd();
            proc.WaitForExit();
            if (proc.ExitCode != 0)
            {
                if (!proc.HasExited)
                    proc.Kill();

                throw new Exception(procErr);
            }
        }
        #endregion

        #region Private
        private string BuildArguments()
        {
            StringBuilder arguments = new StringBuilder();

            // Default Arguments
            if (clean)
                arguments.Append(" -c");
            if (create)
                arguments.Append(" -C");

            // DataMode
            if (dumpMode == DataMode.DataOnly)
                arguments.Append(" -a");
            if (dumpMode == DataMode.SchemaOnly)
                arguments.Append(" -s");

            // backup or restore arguments
            arguments.Append(Arguments());

            // connection arguments
            arguments.Append(ConnectionArguments());

            return arguments.ToString();
        }

        private string ConnectionArguments()
        {
            StringBuilder arguments = new StringBuilder();

            arguments.Append(" -h " + host);
            arguments.Append(" -p " + port.ToString());
            arguments.Append(" -U " + username);
            arguments.Append(" " + database);

            return arguments.ToString();
        }
        #endregion
    }
}
